﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using DirectCanvas;
using DirectCanvas.Misc;
using System.Diagnostics;
using DirectCanvas.Brushes;
using FarseerPhysics.Dynamics;
using DirectCanvas.Shapes;
using Blake.NUI.WPF.Utility;
using System.Windows.Threading;
using System.Threading.Tasks;
using KinectBender.Shaders;

namespace KinectBender
{
    class CPUPhysicsScene : IDisposable
    {
        #region Fields

        bool isRendering = false;

        Size imageSize;
        Size particleSize;
        DirectCanvasFactory factory;
        WPFPresenter presenter;
        DrawingLayer intermediateLayer;
        Brush particleBrush;

        int frameCount;
        Stopwatch fpsTimer;

        List<GPUParticle> particles;

        #endregion

        #region Properties

        public double FPS { get; private set; }

        public bool IsDisposed { get; private set; }

        public System.Windows.Media.ImageSource ImageSource
        {
            get
            {
                return presenter.ImageSource;
            }
        }

        #endregion

        #region Events

        public event EventHandler FPSUpdated;

        protected void OnFPSUpdated()
        {
            if (FPSUpdated == null)
                return;
            FPSUpdated(this, EventArgs.Empty);
        }

        #endregion

        #region Constructors

        public CPUPhysicsScene(int width, int height)
        {
            IsDisposed = false;
            Init(new Size(width, height));
        }

        ~CPUPhysicsScene()
        {
            Dispose();
        }

        #endregion

        #region Public Methods
        
        internal void Start()
        {
            CompositionTargetEx.FrameUpdating += CompositionTargetEx_FrameUpdating;
        }

        internal void Stop()
        {
            CompositionTargetEx.FrameUpdating -= CompositionTargetEx_FrameUpdating;
        }

        #endregion

        #region Private Methods

        void CountFrames()
        {
            frameCount++;
            if (fpsTimer == null)
            {
                fpsTimer = new Stopwatch();
                fpsTimer.Start();
            }

            if (fpsTimer.Elapsed.TotalSeconds > 1)
            {
                FPS = frameCount / fpsTimer.Elapsed.TotalSeconds;
                OnFPSUpdated();
                fpsTimer.Restart();
                frameCount = 0;
            }
        }

        void Init(Size imageSize)
        {
            particleSize = new Size(20, 20);
            this.imageSize = imageSize;

            factory = new DirectCanvasFactory();
            presenter = new WPFPresenter(factory, imageSize.Width, imageSize.Height);
            intermediateLayer = factory.CreateDrawingLayer(particleSize.Width, particleSize.Height);

            particleBrush = factory.CreateSolidColorBrush(new DirectCanvas.Color4(.25f, 0.2f, 0.4f, 1f));

            intermediateLayer.Clear(new DirectCanvas.Color4(0, 0, 0, 0));
            intermediateLayer.BeginDraw();

            Ellipse ellipse = new Ellipse(new PointF(particleSize.Width / 2, particleSize.Height / 2), particleSize.Width / 2, particleSize.Height / 2);
            intermediateLayer.FillEllipse(particleBrush, ellipse);
            intermediateLayer.EndDraw();

            particles = new List<GPUParticle>();

            Random random = new Random();
            float radius = 6;
            for (int i = 0; i < 5000; i++)
            {
                var particle = new GPUParticle()
                    {
                        Mass = 1f
                    };
                var dist = random.NextDouble() * radius;
                var angle = random.NextDouble() * 360;
                //angle = 0;
                var angleoffset = random.NextDouble() * 20 - 10;
                var pos = MathUtility.RotateVector(new System.Windows.Vector(0, dist), angle);
                if (pos.Y < -5)
                {
                    pos.Y += radius;
                }
                var vel = MathUtility.RotateVector(new System.Windows.Vector(0, dist), angle + 90 + angleoffset);
                particle.Position = new SlimDX.Vector3((float)pos.X, (float)pos.Y, 0f);
                particle.Velocity = new SlimDX.Vector3((float)vel.X, (float)vel.Y, 0f);
                particle.Radius = (float)MathUtility.MapValue(particleSize.Width, 0, imageSize.Width, 0, 5);

                particles.Add(particle);
            }

        }

        void CompositionTargetEx_FrameUpdating(object sender, DeltaRenderingEventArgs e)
        {
            Step(e.Delta);
        }


        private void Step(TimeSpan delta)
        {
            Update(delta);

            Render();
            CountFrames();
        }

        private void Update(TimeSpan delta)
        {
            float dt = (float)delta.TotalSeconds;
            float factor = 1f;
            //Parallel.ForEach(particles, particle =>
            foreach (var particle in particles)
            {
                //double angle = MathUtility.Get2DAngle(new System.Windows.Vector(0, 1),
                //                                      new System.Windows.Vector(particle.Position.X, particle.Position.Y));
                //var center = MathUtility.RotateVector(new System.Windows.Vector(0, .1), angle + 90);

                particle.Force = new SlimDX.Vector3();
                //PointF centerF = new PointF((float)center.X, (float)center.Y);
                PointF centerF = new PointF();
                //particle.ApplyForce(new SlimDX.Vector3((centerF.X - particle.Position.X) / factor,
                //                                       (centerF.Y - particle.Position.Y) / factor, 0f));
                particle.ApplyForce(new SlimDX.Vector3(0, -5f, 0));

                if (particle.Position.Y < -5)
                {
                    particle.Position.Y = -5;
                    particle.Velocity.Y *= -0.0f;
                }
                if (particle.Position.X < -8)
                {
                    particle.Position.X = -8;
                    particle.Velocity.X *= -0.0f;
                }
                if (particle.Position.X > 8)
                {
                    particle.Position.X = 8;
                    particle.Velocity.X *= -0.0f;
                }
            }

            int count = particles.Count;
            for (int i = 0; i < count; i++)
            {
                for (int j = i + 1; j < count; j++)
                {
                    var pA = particles[i];
                    var pB = particles[j];
                    var dist = pA.Position - pB.Position;
                    var vel = pA.Velocity - pB.Velocity;
                    var collision = dist.Length() - (pA.Radius + pB.Radius);
                    if (collision < 0)
                    {
                        //dist.Normalize();
                        //dist *= collision * 100f;
                        if (Math.Abs(dist.X) < 0.1f)
                            dist.X = 0.1f * Math.Sign(dist.X);
                        if (Math.Abs(dist.Y) < 0.1f)
                            dist.Y = 0.1f * Math.Sign(dist.Y);
                        var force = new SlimDX.Vector3(5 / dist.X, 5 / dist.Y, 0f) * .3f;
                        pB.ApplyForce(-force);
                        pA.ApplyForce(force);
                        //pB.Velocity *= .3f;
                        //pA.Velocity *= .3f;
                    }
                }
            }

            Parallel.ForEach(particles, particle =>
            //foreach (var particle in particles)
            {
                particle.Integrate(dt);
            }
            );
        }

        private void Render()
        {
            if (isRendering)
                return;
            isRendering = true;
            presenter.Clear(new DirectCanvas.Color4(1, 0, 0, 0));

            presenter.BeginCompose();

            var sourceArea = new RectangleF(0, 0, particleSize.Width, particleSize.Height);
            var rot = new RotationParameters();
            var tint = new Color4(1, 1, 1, 1);
            foreach (var particle in particles)
            {
                var position = PositionToScreen(particle.Position);
                var destinationArea = new RectangleF(position.X - particleSize.Width / 2, position.Y - particleSize.Height / 2,
                                                    particleSize.Width, particleSize.Height);
                presenter.ComposeLayer(intermediateLayer, sourceArea, destinationArea, rot, tint);
            }

            presenter.EndCompose();
            presenter.Present();
            isRendering = false;
        }

        private PointF PositionToScreen(SlimDX.Vector3 position)
        {
            float x = position.X;
            float y = position.Y;

            x = (float)MathUtility.MapValue(x, -10, 10, 0, imageSize.Width);
            y = (float)MathUtility.MapValue(y, 10, -10, 0, imageSize.Height);

            return new PointF(x, y);
        }

        #endregion

        public void Dispose()
        {
            if (IsDisposed)
                return;
            IsDisposed = true;

            if (intermediateLayer != null) { intermediateLayer.Dispose(); intermediateLayer = null; }
            if (intermediateLayer != null) { intermediateLayer.Dispose(); intermediateLayer = null; }
            if (particleBrush != null) { particleBrush.Dispose(); particleBrush = null; }
            if (presenter != null) { presenter.Dispose(); presenter = null; }
            if (factory != null) { factory.Dispose(); factory = null; }
        }

    }
}
